//
// Created by changlei on 2022/4/27.
//

import Foundation
import UIKit
import WebKit


private let expandedHeight = 100.0
private let collapsedHeight = 18.0
private let keyboardCollapsedHeight = 24.0
private let shadowColor = UIColor { collection in
    collection.userInterfaceStyle == .dark ? .init(hex: 0xFF49494A) : .separator
}
private let secondaryBarBackgroundColor = UIColor { collection in
    collection.userInterfaceStyle == .dark ? .init(hex: 0xFF6E6E6F) : .white
}

typealias UIPlainTabBarItemHandler = (UIPlainTabBarItem) -> Void

class UIPlainTabBar: UIView {
    lazy private var backgroundView: UITabBar = {
        let uiTabBar = UITabBar()
        uiTabBar.barTintColor = .secondarySystemBackground
        return uiTabBar
    }()
    lazy private var websiteBar: UIWebsiteBar = {
        let uiWebsiteBar = UIWebsiteBar()
        layoutStateObservation?.invalidate()
        layoutStateObservation = uiWebsiteBar.observe(\.layoutState) { (websiteBar, change) in
            if websiteBar.layoutState == .locked {
                self.changeLayout(true)
            }
        }
        return uiWebsiteBar
    }()
    lazy private var operationBar: UIOperationBar = {
        UIOperationBar()
    }()

    private var urlObservation: NSKeyValueObservation?
    private var contentOffsetObservation: NSKeyValueObservation?
    private var layoutStateObservation: NSKeyValueObservation?
    private var webView: WKWebView?

    private var lastLayoutState: UIWebsiteBar.LayoutState!
    private var lastBottomInset: Double!
    private var lastSafeAreaBottom: Double = 0.0

    override init(frame: CGRect) {
        super.init(frame: frame)
        didInit()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        didInit()
    }

    func didInit() {
        addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onPressed)))

        addSubview(backgroundView)
        backgroundView.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }

        addSubview(websiteBar)
        websiteBar.snp.makeConstraints { make in
            make.leading.trailing.equalToSuperview().inset(UIEdgeInsets(top: 0, left: 32, bottom: 0, right: 32))
            make.top.equalToSuperview().offset(8)
            make.height.equalTo(44)
            make.bottom.lessThanOrEqualToSuperview().offset(8)
        }

        addSubview(operationBar)
        operationBar.snp.makeConstraints { make in
            make.leading.trailing.equalToSuperview()
            make.top.equalTo(websiteBar.snp.bottom)
            make.bottom.equalTo(safeAreaLayoutGuide.snp.bottom)
        }

        keyboardLayoutGuide.followsUndockedKeyboard = true

        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame(_:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
    }

    override func didMoveToSuperview() {
        super.didMoveToSuperview()
        snp.makeConstraints { make in
            make.leading.trailing.equalToSuperview()
            make.height.equalTo(safeAreaInsets.bottom + expandedHeight)
            make.bottom.equalToSuperview().offset(0)
        }
    }

    override func safeAreaInsetsDidChange() {
        super.safeAreaInsetsDidChange()
        snp.updateConstraints { make in
            make.height.equalTo(safeAreaInsets.bottom + expandedHeight)
        }
        if (safeAreaInsets.bottom > 0) {
            lastSafeAreaBottom = safeAreaInsets.bottom
        }
    }

    @objc private func keyboardWillChangeFrame(_ sender: NSNotification) {
        guard let view = viewController?.view else {
            return
        }
        UIView.animate(withDuration: sender.keyboardAnimationDuration) { [self] in
            let keyboardHeight = view.bounds.height - sender.keyboardFrameEnd.origin.y
            if keyboardHeight > 0 {
                let safeAreaBottom = keyboardHeight > expandedHeight ? lastSafeAreaBottom : 0.0
                let boundsHeight = expandedHeight + lastSafeAreaBottom - transform.ty - safeAreaBottom
                let offset = boundsHeight - keyboardHeight - keyboardCollapsedHeight
                snp.updateConstraints { make in
                    make.bottom.equalToSuperview().offset(offset)
                }
                if websiteBar.layoutState != .keyboard {
                    lastBottomInset = webView?.scrollView.contentInset.bottom ?? 0
                    webView?.scrollView.contentInset.bottom = keyboardCollapsedHeight
                    webView?.scrollView.verticalScrollIndicatorInsets.bottom = keyboardCollapsedHeight
                    lastLayoutState = websiteBar.layoutState
                    websiteBar.layoutState = .keyboard
                }
            } else {
                snp.updateConstraints { make in
                    make.bottom.equalToSuperview().offset(0)
                }
                webView?.scrollView.contentInset.bottom = lastBottomInset
                webView?.scrollView.verticalScrollIndicatorInsets.bottom = lastBottomInset
                websiteBar.layoutState = lastLayoutState
            }
            superview?.layoutIfNeeded()
        }
    }

    override func didMoveToWindow() {
        super.didMoveToWindow()
        initializeWebView()
    }

    private func initializeWebView() {
        urlObservation?.invalidate()
        contentOffsetObservation?.invalidate()
        guard let webView = viewController?.view.findSubview(WKWebView.self) else {
            return
        }
        self.webView = webView
        let scrollView = webView.scrollView
        scrollView.contentInset.bottom = expandedHeight
        scrollView.verticalScrollIndicatorInsets.bottom = expandedHeight
        urlObservation = webView.observe(\.url, options: [.new]) { (scrollView, change) in
            self.didUrlChanged(webView.url, webView: webView)
        }
        contentOffsetObservation = scrollView.observe(\.contentOffset, options: [.new, .old]) { [self] (scrollView, change) in
            if !scrollView.isTracking || websiteBar.layoutState == .locked || websiteBar.layoutState == .keyboard {
                return
            }
            var oldValue = change.oldValue
            oldValue?.y -= scrollView.contentInset.top
            var contentOffset = scrollView.contentOffset
            contentOffset.y -= scrollView.contentInset.top
            didContentOffsetChanged(contentOffset, old: oldValue, scrollView: scrollView)
        }
    }

    private func didUrlChanged(_ url: URL?, webView: WKWebView) {
        changeLayoutIfNeeds(false)
    }

    private func didContentOffsetChanged(_ contentOffset: CGPoint, old: CGPoint?, scrollView: UIScrollView) {
        let contentHeight = scrollView.contentSize.height
        let viewportDimension = scrollView.bounds.height
        let maxScrollExtent = contentHeight - viewportDimension
        let offsetY = contentOffset.y
        guard let old = old, offsetY >= 0, maxScrollExtent > 0 else {
            return
        }
        let delta = offsetY - old.y
        let safeAreaBottom = safeAreaInsets.bottom
        let insetBottom = scrollView.contentInset.bottom
        let resolvedOffsetY = offsetY - safeAreaBottom - insetBottom
        let resolvedGap = expandedHeight * contentHeight / viewportDimension

        var newHeight: Double?
        if delta > 0, offsetY > 0, resolvedOffsetY < maxScrollExtent - resolvedGap {
            newHeight = safeAreaBottom + collapsedHeight
        } else if delta < 0 || resolvedOffsetY >= maxScrollExtent {
            newHeight = safeAreaBottom + expandedHeight
        }
        guard let newHeight = newHeight else {
            return
        }

        changeLayoutIfNeeds(newHeight - safeAreaBottom == collapsedHeight)
    }

    private func changeLayoutIfNeeds(_ isCollapsed: Bool, unlock: Bool = false) {
        let layoutState = websiteBar.layoutState
        if layoutState == .keyboard || (layoutState == .locked && !unlock) {
            return
        }
        changeLayout(isCollapsed, unlock: unlock)
    }

    private func changeLayout(_ isCollapsed: Bool, unlock: Bool = false) {
        let safeAreaBottom = safeAreaInsets.bottom
        let newHeight = safeAreaBottom + (isCollapsed ? collapsedHeight : expandedHeight)
        let translationY = bounds.height - newHeight
        let newBottomInset = newHeight - safeAreaBottom
        let isCollapsed = newBottomInset == collapsedHeight

        if transform.ty == translationY {
            return
        }

        UIView.animate(withDuration: 0.3) { [self] in
            transform = .init(translationX: 0, y: translationY)
            webView?.scrollView.contentInset.bottom = newBottomInset
            webView?.scrollView.verticalScrollIndicatorInsets.bottom = newBottomInset
            if websiteBar.layoutState != .keyboard && (websiteBar.layoutState != .locked || unlock) {
                websiteBar.layoutState = isCollapsed ? .collapsed : .expanded
            }
            backgroundView.barTintColor = isCollapsed ? .init(hex: 0xFFEBEBEB) : .secondarySystemBackground
            backgroundView.isTranslucent = !isCollapsed
        }
    }

    @objc private func onPressed() {
        if websiteBar.layoutState == .expanded {
            return
        }
        changeLayoutIfNeeds(false, unlock: true)
    }

    deinit {
        urlObservation?.invalidate()
        urlObservation = nil
        contentOffsetObservation?.invalidate()
        contentOffsetObservation = nil
        layoutStateObservation?.invalidate()
        layoutStateObservation = nil
        NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
    }
}

class UIPlainTabBarItem: NSObject {
    lazy fileprivate(set) var button: UIButton = {
        let uiButton = UIButton(type: .system)
        uiButton.addTarget(self, action: #selector(onPressed), for: .touchUpInside)
        return uiButton
    }()
    var action: UIPlainTabBarItemHandler?
    var menu: UIMenu? {
        get {
            button.menu
        }
        set {
            button.menu = newValue
        }
    }
    var image: UIImage? {
        get {
            button.image(for: .normal)
        }
        set {
            button.setImage(newValue, for: .normal)
        }
    }
    var isEnabled: Bool {
        get {
            button.isEnabled
        }
        set {
            button.isEnabled = newValue
        }
    }

    convenience init(_ image: UIImage?, action: UIPlainTabBarItemHandler? = nil, menu: UIMenu? = nil) {
        self.init()
        self.image = image
        self.action = action
        self.image = image
        self.menu = menu
    }

    @objc func onPressed() {
        if let action = action {
            action(self)
        }
    }
}

private class UIWebsiteBar: UIView {
    lazy private var contentButton: UIButton = {
        let uiButton = UIButton(type: .custom)
        uiButton.layer.cornerRadius = 12
        uiButton.clipsToBounds = true
        contentButtonHighlightedObservation = uiButton.observe(\.isHighlighted) { (button, change) in
            UIView.animate(withDuration: 0.15) {
                uiButton.backgroundColor = uiButton.isHighlighted ? .systemFill : .clear
            }
        }
        return uiButton
    }()
    lazy private var fontSizeButton: UIFontSizeButton = {
        let button = UIFontSizeButton(type: .custom)
        button.onHideToolbar = {
            self.layoutState = .locked
        }
        return button
    }()
    lazy private var refreshButton: UIRefreshButton = {
        UIRefreshButton(type: .custom)
    }()
    lazy private var speedButton: UISpeedButton = {
        UISpeedButton()
    }()
    lazy private var progressView: UIProgressView = {
        let uiProgressView = UIProgressView(progressViewStyle: .bar)
        uiProgressView.alpha = 0
        uiProgressView.progress = 0
        return uiProgressView
    }()
    lazy private var interaction: UIInteraction = {
        UIContextMenuInteraction(delegate: interactionDelegate)
    }()

    private var urlObservation: NSKeyValueObservation?
    private var progressObservation: NSKeyValueObservation?
    private var webView: WKWebView?
    private var interactionDelegate = UIWebsiteBarInteractionDelegate()
    private var contentButtonHighlightedObservation: NSKeyValueObservation?

    @objc dynamic var layoutState: LayoutState = .expanded {
        didSet {
            didLayoutStateChanged()
        }
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        didInit()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        didInit()
    }

    func didInit() {
        layer.cornerRadius = 12
        layer.shadowColor = shadowColor.cgColor
        layer.shadowRadius = 10
        layer.shadowOpacity = 0.6
        layoutState = .expanded

        addSubview(contentButton)
        contentButton.snp.makeConstraints { make in
            make.edges.equalToSuperview()
        }

        addSubview(fontSizeButton)
        fontSizeButton.snp.makeConstraints { make in
            make.leading.top.bottom.equalToSuperview()
            make.width.equalTo(snp.height).offset(8)
        }

        addSubview(refreshButton)
        refreshButton.snp.makeConstraints { make in
            make.trailing.top.bottom.equalToSuperview()
            make.width.equalTo(snp.height)
        }

        addSubview(speedButton)
        speedButton.snp.makeConstraints { make in
            make.trailing.top.bottom.equalToSuperview()
            make.width.equalTo(snp.height)
        }
    }

    override func didMoveToWindow() {
        super.didMoveToWindow()
        initializeWebView()
    }

    private func initializeWebView() {
        urlObservation?.invalidate()
        progressObservation?.invalidate()
        guard let webView = viewController?.view.findSubview(WKWebView.self) else {
            return
        }
        self.webView = webView
        urlObservation = webView.observe(\.url, options: [.new, .initial]) { (webView, change) in
            self.didUrlChanged(webView.url, webView: webView)
        }
        progressObservation = webView.observe(\.estimatedProgress, options: [.new, .initial, .old]) { (webView, change) in
            self.didProgressChanged(webView.estimatedProgress, old: change.oldValue ?? 0.0, webView: webView)
        }
    }

    private func didUrlChanged(_ url: URL?, webView: WKWebView) {
        fontSizeButton.isHidden = url == nil
        refreshButton.isHidden = url == nil
        speedButton.isHidden = url != nil

        let websiteBarMenu = UIWebsiteBarMenu()
        websiteBarMenu.url = url
        interactionDelegate.contextMenu = websiteBarMenu

        var borderless = UIButton.Configuration.plain()
        borderless.background = .clear()
        if let url = url {
            borderless.title = url.host
            borderless.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { container in
                AttributeContainer([
                    NSAttributedString.Key.foregroundColor: UIColor.label,
                ])
            }
            borderless.imagePlacement = .leading
            borderless.imagePadding = 4
            borderless.image = UIImage(systemName: "lock.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .small))
            borderless.imageColorTransformer = .grayscale
        } else {
            borderless.title = "搜索或输入网站"
            borderless.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { container in
                AttributeContainer([
                    NSAttributedString.Key.foregroundColor: UIColor.systemGray,
                ])
            }
            borderless.imagePlacement = .leading
            borderless.imagePadding = 8
            borderless.image = UIImage(systemName: "magnifyingglass", withConfiguration: UIImage.SymbolConfiguration(scale: .medium))
            borderless.imageColorTransformer = .grayscale
            borderless.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 12, bottom: 0, trailing: 8)
            borderless.titleAlignment = .leading
        }
        contentButton.configuration = borderless
        contentButton.contentHorizontalAlignment = url == nil ? .leading : .center
    }

    private func didProgressChanged(_ progress: Double, old: Double, webView: WKWebView) {
        let newProgress: Float
        if progress != 1.0 && (old == 1.0 || old == 0.0) {
            newProgress = 0.1
        } else {
            newProgress = Float(progress)
        }
        let isLoading = newProgress != 1.0
        let delay = isLoading ? 0 : max(progress - old + 0.3, 0.5)
        UIView.animate(withDuration: isLoading ? 0.0 : 0.3, delay: delay) {
            self.progressView.alpha = isLoading ? 1.0 : 0.0
        } completion: { _ in
            if !isLoading {
                self.progressView.progress = 0.0
            }
        }
        progressView.setProgress(newProgress, animated: true)
    }

    private func didLayoutStateChanged() {
        progressView.removeFromSuperview()
        progressView.snp.removeConstraints()
        removeInteraction(interaction)
        var configuration = contentButton.configuration ?? UIButton.Configuration.plain()
        switch layoutState {
        case .expanded:
            addInteraction(interaction)
            backgroundColor = secondaryBarBackgroundColor
            isUserInteractionEnabled = true
            fontSizeButton.alpha = 1.0
            refreshButton.alpha = 1.0
            contentButton.transform = .identity
            contentButton.isEnabled = true
            contentButton.isExclusiveTouch = false
            contentButton.isUserInteractionEnabled = true
            contentButton.isPointerInteractionEnabled = true
            configuration.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { container in
                AttributeContainer([
                    NSAttributedString.Key.foregroundColor: UIColor.label,
                ])
            }
            configuration.imageColorTransformer = .grayscale

            contentButton.addSubview(progressView)
            progressView.snp.makeConstraints { make in
                make.leading.trailing.bottom.equalToSuperview()
                make.height.equalTo(2.8)
            }
            break
        case .collapsed, .locked:
            backgroundColor = .clear
            isUserInteractionEnabled = false
            fontSizeButton.alpha = 0.0
            refreshButton.alpha = 0.0
            contentButton.transform = .init(translationX: 0, y: -10).scaledBy(x: 0.8, y: 0.8)
            contentButton.isEnabled = false
            contentButton.isExclusiveTouch = true
            contentButton.isUserInteractionEnabled = false
            contentButton.isPointerInteractionEnabled = false
            configuration.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { container in
                AttributeContainer([
                    NSAttributedString.Key.foregroundColor: UIColor.label.resolvedColor(with: .init(userInterfaceStyle: .light)),
                ])
            }
            configuration.imageColorTransformer = .init { (color: UIColor) -> UIColor in
                .gray
            }

            superview?.addSubview(progressView)
            progressView.snp.makeConstraints { make in
                make.leading.trailing.top.equalToSuperview()
                make.height.equalTo(1.8)
            }
            break
        case .keyboard:
            backgroundColor = .clear
            isUserInteractionEnabled = false
            fontSizeButton.alpha = 0.0
            refreshButton.alpha = 0.0
            contentButton.transform = .init(translationX: 0, y: -18).scaledBy(x: 0.7, y: 0.7)
            contentButton.isEnabled = false
            contentButton.isExclusiveTouch = true
            contentButton.isUserInteractionEnabled = false
            contentButton.isPointerInteractionEnabled = false
            configuration.titleTextAttributesTransformer = UIConfigurationTextAttributesTransformer { container in
                AttributeContainer([
                    NSAttributedString.Key.foregroundColor: UIColor.label.resolvedColor(with: .init(userInterfaceStyle: .light)),
                ])
            }
            configuration.imageColorTransformer = .init { (color: UIColor) -> UIColor in
                .gray
            }

            superview?.addSubview(progressView)
            progressView.snp.makeConstraints { make in
                make.leading.trailing.top.equalToSuperview()
                make.height.equalTo(1.8)
            }
            break
        }
        contentButton.configuration = configuration
    }

    deinit {
        urlObservation?.invalidate()
        urlObservation = nil
        progressObservation?.invalidate()
        progressObservation = nil
        contentButtonHighlightedObservation?.invalidate()
        contentButtonHighlightedObservation = nil
    }

    @objc enum LayoutState: Int {
        case expanded
        case collapsed
        case locked
        case keyboard
    }
}

private class UIWebsiteBarInteractionDelegate: NSObject, UIContextMenuInteractionDelegate {
    fileprivate var contextMenu: UIMenu?

    func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
        if let contextMenu = contextMenu {
            return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { elements in
                contextMenu
            }
        } else {
            return nil
        }
    }
}

private class UIFontSizeButton: UIButton {
    var onHideToolbar: (() -> Void)? {
        didSet {
            if let menu = menu as? UIFontSizeMenu {
                menu.onHideToolbar = onHideToolbar
            }
        }
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        didInit()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        didInit()
    }

    func didInit() {
        let image = UIImage(systemName: "textformat.size")?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(scale: .medium))
        setImage(image?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(hierarchicalColor: .label)), for: .normal)
        setImage(image?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(hierarchicalColor: .placeholderText)), for: .highlighted)
        setImage(image?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(hierarchicalColor: .placeholderText)), for: .disabled)

        showsMenuAsPrimaryAction = true
        menu = UIFontSizeMenu()
    }
}

private class UIRefreshButton: UIButton {
    @objc dynamic var refreshState: RefreshState = RefreshState.normal {
        didSet {
            let image: UIImage!
            switch refreshState {
            case .normal:
                image = UIImage(systemName: "arrow.clockwise")?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(scale: .medium))
                break
            case .refreshing:
                image = UIImage(systemName: "xmark")?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(scale: .medium))
                break
            }
            setImage(image?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(hierarchicalColor: .label)), for: .normal)
            setImage(image?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(hierarchicalColor: .placeholderText)), for: .highlighted)
            setImage(image?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(hierarchicalColor: .placeholderText)), for: .disabled)
        }
    }

    private var estimatedProgressObservation: NSKeyValueObservation?
    private var webView: WKWebView?

    override init(frame: CGRect) {
        super.init(frame: frame)
        didInit()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        didInit()
    }

    func didInit() {
        refreshState = .normal
        addTarget(self, action: #selector(refresh(_:)), for: .touchUpInside)
    }

    override func didMoveToWindow() {
        super.didMoveToWindow()
        initializeWebView()
    }

    private func initializeWebView() {
        estimatedProgressObservation?.invalidate()
        guard let webView = viewController?.view.findSubview(WKWebView.self) else {
            return
        }
        self.webView = webView
        estimatedProgressObservation = webView.observe(\.estimatedProgress, options: [.new, .initial]) { (webView, change) in
            self.didProgressChanged(webView.estimatedProgress, webView: webView)
        }
    }

    private func didProgressChanged(_ progress: Double, webView: WKWebView) {
        if webView.estimatedProgress == 1.0 {
            refreshState = .normal
        } else {
            refreshState = .refreshing
        }
    }

    @objc private func refresh(_ sender: UIRefreshButton) {
        guard let webView = webView, webView.url != nil else {
            return
        }
        if sender.refreshState == .normal {
            sender.refreshState = .refreshing
            webView.reload()
        } else {
            sender.refreshState = .normal
            webView.stopLoading()
        }
    }

    deinit {
        estimatedProgressObservation?.invalidate()
        estimatedProgressObservation = nil
    }

    @objc enum RefreshState: Int {
        case normal
        case refreshing
    }
}

private class UISpeedButton: UIButton {
    override init(frame: CGRect) {
        super.init(frame: frame)
        didInit()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        didInit()
    }

    func didInit() {
        let image = UIImage(systemName: "mic.fill")?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(scale: .medium))
        setImage(image?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(hierarchicalColor: .systemGray)), for: .normal)
        setImage(image?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(hierarchicalColor: .placeholderText)), for: .highlighted)
        setImage(image?.applyingSymbolConfiguration(UIImage.SymbolConfiguration(hierarchicalColor: .placeholderText)), for: .disabled)
    }
}

private class UIOperationBar: UIView {
    lazy private var backwardItem: UIPlainTabBarItem = {
        let barItem = createTabItem(systemName: "chevron.backward") { [self] item in
            if let webView = webView, webView.canGoBack {
                webView.goBack()
            } else {
                if let navigationController = viewController?.navigationController {
                    navigationController.popViewController(animated: true)
                } else {
                    viewController?.dismiss(animated: true)
                }
            }
        }
        barItem.isEnabled = false
        return barItem
    }()
    lazy private var forwardItem: UIPlainTabBarItem = {
        let barItem = createTabItem(systemName: "chevron.forward") { [self] item in
            if let webView = webView {
                webView.goForward()
            }
        }
        barItem.isEnabled = false
        return barItem
    }()
    lazy private var shareItem: UIPlainTabBarItem = {
        createTabItem(systemName: "square.and.arrow.up") { [self] item in
            if let webView = webView {
                Modules.share(webView.url)
            }
        }
    }()
    lazy private var readingListItem: UIPlainTabBarItem = {
        let tabItem = createTabItem(systemName: "book") { [self] item in
        }
        tabItem.menu = UIReadingListMenu()
        return tabItem
    }()
    lazy private var stackItem: UIPlainTabBarItem = {
        let tabItem = createTabItem(systemName: "square.on.square") { [self] item in
        }
        tabItem.menu = UIStackMenu()
        return tabItem
    }()
    lazy private var buttonsBar: UIButtonsBar = {
        let uiButtonsBar = UIButtonsBar()
        uiButtonsBar.items = [
            backwardItem,
            forwardItem,
            shareItem,
            readingListItem,
            stackItem,
        ]
        return uiButtonsBar
    }()

    private var urlObservation: NSKeyValueObservation?
    private var canGoBackObservation: NSKeyValueObservation?
    private var canGoForwardObservation: NSKeyValueObservation?
    private var webView: WKWebView?

    override init(frame: CGRect) {
        super.init(frame: frame)
        didInit()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        didInit()
    }

    func didInit() {
        addSubview(buttonsBar)
        buttonsBar.snp.makeConstraints { make in
            make.top.bottom.equalToSuperview()
            make.leading.equalToSuperview().offset(-20)
            make.trailing.equalToSuperview().offset(20)
        }
    }

    override func didMoveToWindow() {
        super.didMoveToWindow()
        initializeWebView()
    }

    private func initializeWebView() {
        urlObservation?.invalidate()
        canGoBackObservation?.invalidate()
        canGoForwardObservation?.invalidate()
        guard let webView = viewController?.view.findSubview(WKWebView.self) else {
            return
        }
        self.webView = webView
        urlObservation = webView.observe(\.url, options: [.new, .initial]) { (webView, change) in
            self.didUrlChanged(webView.url, webView: webView)
        }
        canGoBackObservation = webView.observe(\.canGoBack, options: [.new, .initial]) { (webView, change) in
            self.didCanGoBackChanged(webView.canGoBack, webView: webView)
        }
        canGoForwardObservation = webView.observe(\.canGoForward, options: [.new, .initial]) { (webView, change) in
            self.didCanGoForwardChanged(webView.canGoForward, webView: webView)
        }
    }

    private func didUrlChanged(_ url: URL?, webView: WKWebView) {
        shareItem.isEnabled = url != nil
    }

    func didCanGoBackChanged(_ canGoBack: Bool, webView: WKWebView) {
        backwardItem.isEnabled = canGoBack
        viewController?.canGoBack { canGoBackLastController in
            self.backwardItem.isEnabled = canGoBack || canGoBackLastController
        }
    }

    func didCanGoForwardChanged(_ canGoForward: Bool, webView: WKWebView) {
        forwardItem.isEnabled = canGoForward
    }

    private func createTabItem(systemName: String, action: @escaping UIPlainTabBarItemHandler) -> UIPlainTabBarItem {
        let barItem = UIPlainTabBarItem()
        barItem.action = action
        let configuration = UIImage.SymbolConfiguration(pointSize: 17, weight: .regular)
        barItem.image = UIImage(systemName: systemName, withConfiguration: configuration)
        return barItem
    }

    deinit {
        urlObservation?.invalidate()
        urlObservation = nil
        canGoBackObservation?.invalidate()
        canGoBackObservation = nil
        canGoForwardObservation?.invalidate()
        canGoForwardObservation = nil
    }
}

private extension NSNotification {
    var keyboardAnimationDuration: Double {
        userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double ?? 0.25
    }

    var keyboardFrameEnd: CGRect {
        (userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? AnyObject)?.cgRectValue ?? .zero
    }
}